/*
	mot2bin converts a Motorola hex file to binary.
	Copyright (C) 2012  pioneer suntec
	2012/08/02 leichang
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define	BYTE_OF_1M			(1024*1024)
#define	MASK_OF_1M			(0x000FFFF)
#define MAX_MEMORY_SIZE		(64*BYTE_OF_1M)
#define MAX_LINE_SIZE		(256)

typedef enum{
	FALSE = 0
,	TRUE
} boolean;

unsigned char const version_info[] = "Ver1.2.0";

void usage(void)
{
	fprintf (stderr, 
			 "usage:\n"
			 "mot2bin.exe -i motfile -o binaryfile [-s startaddress][-e endaddress][-c][-d][z][-m size][-v][-V]\n"
			 "\t-i motfile      : Input file name.\n"
			 "\t-o binaryfile   : Output file name.\n"
			 "\t-s startaddress : Start address offset(HEX) default=00000000.\n"
 			 "\t-e endaddress   : End address offset(HEX) default=00000000.\n"
			 "\t-c              : Add check sum each 256 byte.\n"
			 "\t-d              : Display debug information.\n"
			 "\t-z              : Fill with 0 default fill 0xFF.\n"
			 "\t-m size         : TargetMemory SIZE(HEX)(bytes).\n"
			 "\t-v              : Display detail information.\n"
			 "\t-V              : Display version information.\n");
	exit(1);
}

unsigned short calc_checksum(unsigned char const * data, unsigned long size)
{
	unsigned long i;
	unsigned short checksum = 0x0000;

	for(i=0;i<size;i++)
	{
		checksum += (unsigned short)((*data) & 0x00FF);
		data++;
	}
	return checksum;
}

int write_binary_file(unsigned char* memory, unsigned long write_size, FILE* bin_file, boolean add_checksum)
{
	unsigned long const block_size = 256;
	unsigned short checksum = 0x0000;

	if( (NULL == bin_file) || (NULL == memory) )
	{
		puts("Can't write binary file!");
		return 0;
	}

	if( add_checksum != TRUE ) 
	{
		fwrite(memory, write_size, 1, bin_file);
	}
	else
	{
		while(write_size > 0)
		{
			fwrite(memory, block_size, 1, bin_file);
			checksum = calc_checksum(memory, block_size);
			fwrite(&checksum, sizeof(checksum), 1, bin_file);
			if(write_size < block_size)
			{
				memory += write_size;
				write_size = 0;
			}
			else
			{
				memory += block_size;
				write_size -= block_size;
			}
		}
	}
	return 1;
}

int main (int argc, char *argv[])
{
	FILE *filein;	/* input files */
	FILE *fileout;	/* output files */
	char MotLine[MAX_LINE_SIZE];	/* line inputed from file */
	char *pInputFile = NULL;	/* point to input file name */
	char *pOutputFile = NULL;	/* point to output file name */
	unsigned long ulStartOffset = 0x00000000;
	unsigned long ulEndOffset = 0xFFFFFFFF;
	unsigned char ucFillData = 0xFF;
	unsigned long ulTargetMemorySize = 0x00000000;
	unsigned long ulBinaryFileSize = 0x00000000;
	boolean add_checksum = FALSE;
	boolean display_debug_information = FALSE;
	boolean disp_detail_info = FALSE; /* display detail information */

	/* flag that a file was read */
	boolean EnableChecksumError = TRUE;
	boolean StatusChecksumError = FALSE;

	/* cmd-line parameter # */
	char *p;

	unsigned int i;

	/* Application specific */
	unsigned int  Nb_Bytes;
	unsigned int  Write_Bytes;
	unsigned int  Address, Lowest_Address, Highest_Address;
	unsigned int  Phys_Addr, Type;
	unsigned int  temp;
	unsigned char ucData;
	unsigned char Data_Str[MAX_LINE_SIZE];
	unsigned char checksum;

	/* This will hold binary codes translated from hex file. */
	unsigned char *Memory_Block=NULL;

	if( 1 == argc )
	{
		usage();
	}

	/* Analys parameter */
	for( i = 1; i < argc; i++ )
	{
		if( '-' == argv[i][0] )
		{
			switch( argv[i][1] )
			{
				/* Add check sum each 256 bytes */
				case 'c':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						add_checksum = TRUE;
					}
					break;

				/* Display debug information */
				case 'd':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						display_debug_information = TRUE;
					}
					break;

				/* input file name */
				case 'i':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						pInputFile = argv[++i];
					}
					break;

				/* output file name */
				case 'o':
					if( argv[i][2] != '\0' )
					{
						usage();
					} else
					{
						pOutputFile = argv[++i];
					}
					break;

				/* Start Address */
				case 's':
					if( argv[i][2] != '\0' )
					{
						usage();
					} else
					{
						sscanf(argv[++i], "%x", &ulStartOffset);
					}
					break;

				/* End Address */
				case 'e':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						sscanf(argv[++i], "%x", &ulEndOffset);
					}
					break;

				/* Fill unused data with zero */
				case 'z':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						ucFillData = 0x00;
					}
					break;

				/* Memory Size(Output file size) */
				case 'm':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						sscanf(argv[++i], "%x", &ulTargetMemorySize);
						if( ulTargetMemorySize > MAX_MEMORY_SIZE )
						{
							fprintf(stderr,"Target Memory Size cann't great than 64M.");
							exit(1);
						}
					}
					break;

				/* Display detail information */
				case 'v':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						disp_detail_info = TRUE;
					}
					break;

				/* Display version information */
				case 'V':
					if( argv[i][2] != '\0' )
					{
						usage();
					}
					else
					{
						fprintf (stdout, "mot2bin %s, Copyright (C) 2012 Pioneer Suntec.\n", version_info);
						exit(1);
					}
					break;

				/* Display usage message */
				default:
					usage();
					break;
			} /* switch */
		} /* if option */
	} /* for Param */

	/* Calc the Start Address, End Address and TargetMemorySize */
	if( 0x00000000 == ulTargetMemorySize )	/* No -m parameter */
	{
		if( ulEndOffset != 0xFFFFFFFF )	/* With -e parameter */
		{
			if( ulEndOffset <= ulStartOffset )
			{
				fprintf(stderr, "EndOffset must be bigger than StarOffset!");
				exit(1);
			}
			ulTargetMemorySize = ulEndOffset - ulStartOffset;
			if( (ulTargetMemorySize & MASK_OF_1M) != 0x00000000 )
			{
				ulTargetMemorySize &= ~MASK_OF_1M;
				ulTargetMemorySize += BYTE_OF_1M;
			}
		}
		else	/* No -e parameter */
		{
			ulTargetMemorySize = MAX_MEMORY_SIZE;
			ulEndOffset = ulStartOffset + ulTargetMemorySize;
		}
		ulBinaryFileSize = 0x00000000;
	}
	else	/* With -m parameter */
	{
		unsigned long offset;
		if( ulEndOffset != 0xFFFFFFFF )	/* With -e parameter */
		{
			if( ulEndOffset <= ulStartOffset )
			{
				fprintf(stderr, "EndOffset must be bigger than StarOffset!");
				exit(1);
			}
			offset = ulEndOffset - ulStartOffset;
			if( ulTargetMemorySize < offset )
			{
				ulEndOffset = ulStartOffset + ulTargetMemorySize;
			}
		}
		else	/* No -e parameter */
		{
			ulEndOffset = ulStartOffset + ulTargetMemorySize;
		}
		ulBinaryFileSize = ulTargetMemorySize;
	}

	if( NULL == pInputFile )
	{
		fprintf(stderr, "No input file name!\n");
		usage();
	}

	if( NULL == pOutputFile )
	{
		fprintf(stderr, "No input file name!\n");
		usage();
	}
	
	filein = fopen(pInputFile, "r");
	if( NULL == filein )
	{
		fprintf (stderr, "Input file %s cannot be opened.", pInputFile);
		exit(1);
	}

	fileout = fopen(pOutputFile, "wb");
	if( NULL == fileout )
	{
		fprintf(stderr, "Output file %s cannot be opened.", pOutputFile);
		fclose(filein);
		exit(1);
	}

	/* allocate a buffer */
	Memory_Block = (unsigned char *)malloc(ulTargetMemorySize);
	if( NULL == Memory_Block )
	{
		fclose(filein);
		fclose(fileout);
	}
	
	/* For EPROM or FLASH memory types, fill unused bytes with FF */
	memset(Memory_Block, ucFillData, ulTargetMemorySize);

	/* To begin, assume the lowest address is at the end of the memory.
	subsequent addresses will lower this number. At the end of the input
	file, this value will be the lowest address. */
	Lowest_Address = ulTargetMemorySize - 1;
	Highest_Address = 0;

	/* Now read the file & process the lines. */
	/* Repeat until end of filein */
	do
	{
		/* Read a line from input file. */
		fgets(MotLine, MAX_LINE_SIZE, filein);

		/* Remove carriage return/line feed at the end of line. */
		i = strlen(MotLine)-1;

		if( '\n' == MotLine[i] )
		{
			MotLine[i] = '\0';
		}

		/* Scan starting address and nb of bytes. */
		/* Look at the record type after the 'S' */
		switch( MotLine[1] )
		{
			/* 16 bits address */
			case '1':
				sscanf (MotLine, "S%1x%2x%4x%s", &Type, &Nb_Bytes, &Address, Data_Str);
				checksum = Nb_Bytes + (Address >> 8) + (Address & 0xFF);

				/* Adjust Nb_Bytes for the number of data bytes */
				Nb_Bytes = Nb_Bytes - 3;
				break;

				/* 24 bits address */
			case '2':
				sscanf (MotLine, "S%1x%2x%6x%s", &Type, &Nb_Bytes, &Address, Data_Str);
				checksum = Nb_Bytes + (Address >> 16) + (Address >> 8) + (Address & 0xFF);

				/* Adjust Nb_Bytes for the number of data bytes */
				Nb_Bytes = Nb_Bytes - 4;
				break;

				/* 32 bits address */
			case '3':
				sscanf (MotLine, "S%1x%2x%8x%s", &Type, &Nb_Bytes, &Address, Data_Str);
				checksum = Nb_Bytes + (Address >> 24) + (Address >> 16) + (Address >> 8) + (Address & 0xFF);

				/* Adjust Nb_Bytes for the number of data bytes */
				Nb_Bytes = Nb_Bytes - 5;
				break;

			default:
				Type = 0;
				break;
		}

		p = Data_Str;

		/* If we're reading the last record, ignore it. */
		switch( Type )
		{
			/* Data record */
			case 1:
			case 2:
			case 3:
				if( (Address < ulStartOffset) || (Address >= ulEndOffset) )
				{
					if( disp_detail_info )
					{
						fprintf(stderr, "Data record skipped at %8X\n", Address);
					}
					break;
				}
				else
				{
					Phys_Addr = Address - ulStartOffset;
				}

				/* Check that the physical address stays in the buffer's range. */
				if( Phys_Addr >= ulTargetMemorySize )
				{
					if( disp_detail_info )
					{
						fprintf(stderr, "Data record skipped at %8X\n", Address);
					}
				}
				else
				{
					if( (Phys_Addr + Nb_Bytes) >= ulTargetMemorySize )
					{
						Write_Bytes = ulTargetMemorySize - Phys_Addr;
					}
					else
					{
						Write_Bytes = Nb_Bytes;
					}
					/* Set the lowest address as base pointer. */
					if( Phys_Addr < Lowest_Address )
					{
						Lowest_Address = Phys_Addr;
					}

					/* Same for the top address. */
					temp = Phys_Addr + Nb_Bytes -1;

					if( temp > Highest_Address )
					{
						Highest_Address = temp;
					}

					/* Read the Data bytes. */
					for( i= 0; i < Nb_Bytes; i++ )
					{
						sscanf (p, "%2x", &ucData);
						p += 2;
						if( Write_Bytes > i )
						{
							Memory_Block[Phys_Addr++] = ucData;
						}
						checksum = (checksum + ucData) & 0xFF;
					}

					/* Read the checksum value. */
					sscanf (p, "%2x",&ucData);

					/* Verify checksum value. */
					checksum = (checksum + ucData) & 0xFF;

					if( (checksum != 0xFF) && EnableChecksumError )
					{
						StatusChecksumError = TRUE;
					}
				}
				break;

				/* Ignore all other records */
			default:
				break;
		}
	} while( !feof (filein) );
/*-----------------------------------------------------------------------------*/

	if( 0x00000000 == ulBinaryFileSize )
	{
		ulBinaryFileSize = Highest_Address+1;
	}

	/* write binary file */
	write_binary_file(Memory_Block, ulBinaryFileSize, fileout, add_checksum);

	if( display_debug_information )
	{
		FILE* logfile;

		logfile = fopen("mot2bin.log", "a");
		if( NULL == logfile )
		{
			logfile = stdout;
		}
		fprintf(logfile, "************************************\n");
		fprintf(logfile, "InputFile : %s\n", pInputFile);
		fprintf(logfile, "OutputFile: %s\n", pOutputFile);
		fprintf(logfile, "ulStartOffset       = 0x%08X\n", ulStartOffset);
		fprintf(logfile, "ulEndOffset         = 0x%08X\n", ulEndOffset);
		fprintf(logfile, "ucFillData          = 0x%08X\n", ucFillData);
		fprintf(logfile, "ulTargetMemorySize  = 0x%08X\n", ulTargetMemorySize);
		fprintf(logfile, "Highest_Address     = 0x%08X\n", Highest_Address);
		fprintf(logfile, "Lowest_Address      = 0x%08X\n", Lowest_Address);
		fprintf(logfile, "add_checksum        = 0x%08X\n", add_checksum);
		fprintf(logfile, "ulBinaryFileSize    = 0x%08X\n", ulBinaryFileSize);
		fprintf(logfile, "Last Byte           = 0x%02X\n", Memory_Block[ulBinaryFileSize-1]);
		fprintf(logfile, "Highest Byte        = 0x%02X\n", Memory_Block[Highest_Address]);
		fprintf(logfile, "\n************************************\n\n");
		if( stdout != logfile )
		{
			fclose(logfile);
		}
	}

	free(Memory_Block);

	fclose(filein);
	fclose(fileout);

	if( StatusChecksumError && EnableChecksumError )
	{
		fprintf(stderr, "checksum error detected.\n");
		return 1;
	}

	return 0;
}

